/***********************************************************************************
 * 文件名： mcu_adc.c
 * 作者： 刘言
 * 版本： 3
 * 说明：
 * 		ADC驱动。
 * 修改记录：
 * 	2020/8/19: 初版。 刘言。
 *  2020/11/7: V2。新增扫描转换功能。单次转换现在不用在意ADC中断状态、是否单次模式，已经内部处理。
 *  2020/12/21: V3。mIsBusy增加volatile修饰，解决编译器优化后不再判断mIsBusy导致死机问题。
 *              跨线程（中断）全局变量同步务必加volatile。
***********************************************************************************/
#include "HC32_LyLib.h"	
#include "mcu_adc.h"

#if (ADC_SCAN_CNT > 0)
static volatile bool mIsBusy = false;       // 标记正在转换中（单次转换和扫描转换不能同时进行）
#endif

// ADC初始化，注意没有开启时钟门控
void Adc_Init()
{
    //SYSCTRL_ENABLE_ADC_CLK;
#if (HC32_MCU_SERIES == HC32F005)
    M0P_ADC->CR0 = (ADC_CLKSEL << 2) | (ADC_SAM << 11) | (ADC_SREF << 8) | (ADC_BUFEN << 10);
    M0P_ADC->CR1 = 0;   // 注意复位值不为0
    M0P_ADC->CR2 =  (ADC_SCAN_CNT << 8) | (ADC_SCAN_CHEN_MASK << 0);
    M0P_ADC->CR0_f.ADCEN = 1;   
#else
    M0P_ADC->CR0 = (ADC_CLKSEL << 2) | (ADC_SAM << 12) | (ADC_SREF << 9) | (ADC_BUFEN << 11)
                    |(1 << 14)      // 使能内部参考电压 是否必要待测试
                    ;
    M0P_ADC->CR0_f.EN = 1;
#endif
    M0P_BGR->CR = 0x03;     // 启动内部温度传感器，使能BGR

#if (ADC_SCAN_CNT > 0)
    NVIC_ClearPendingIRQ(ADC_IRQn);        // 清除NVIC中断标志位
    NVIC_SetPriority(ADC_IRQn, 3);         // 优先级 3 （0~3）
    NVIC_EnableIRQ(ADC_IRQn);              // NVIC允许中断
#endif
}

#if (ADC_SCAN_CNT > 0)
// 事件：通知ADC驱动程序 “扫描（连续）转换完成”，请务必在ADC中断中调用，中断标志位未清除（需要ISR中清除）
FORCE_IN_LINE void Adc_ScanCompleted()
{
#if (HC32_MCU_SERIES == HC32F005)   
    M0P_ADC->CR0_f.IE = 0;      // 关闭ADC中断
    M0P_ADC->CR1_f.CT = 0;      // 切换到单次转换模式
#endif
    mIsBusy = false;            // 标记不在扫描（单次转换将跳出等待）
}
#endif

#if (ADC_SCAN_CNT > 0)
// [异步]开始一次扫描（连续）转换，完成后会触发ADC中断，请在ISR中调用处理函数。目前仅支持005。
// 注意：需要先把对应通道的IO口设置为模拟信号模式。
void Adc_StartScan()
{
#if (HC32_MCU_SERIES == HC32F005)   
    if(mIsBusy == false)    //检查是否正在扫描
    {
        mIsBusy = true;                     // 标记正在扫描。（单线程程序中，单次转换函数返回就结束了，所以不需要查忙。多线程程序自行处理互斥）
        M0P_ADC->CR1_f.CT = 1;              // 切换到连续转换模式
        M0P_ADC->ICLR_f.CONT_INTC = 0;      // 清除完成标志位（中断标志位）
        M0P_ADC->CR0_f.STATERST = 1;        // 复位连续转换状态
        M0P_ADC->CR0_f.IE = 1;              // 使能中断
        M0P_ADC->CR0_f.START = 1;           // 开始转换
    }
#endif
}
#endif

// [同步]获取一次转换结果
// 注意：需要先把对应通道的IO口设置为模拟信号模式
u16 Adc_GetOnce(u8 ch)
{
    u16 r;
#if (HC32_MCU_SERIES == HC32F005)   // 005没有单次转换结束中断
#if (ADC_SCAN_CNT > 0)
    while(mIsBusy);     // 等待扫描转换结束，结束后已经是单次模式
    // 必须保证单次转换过程中不会启动扫描转换
    ADC_DISALLOW_INT_START_SCAN;     // 禁止中断中开启扫描转换
#endif
    M0P_ADC->CR0_f.SEL = ch;
    M0P_ADC->CR0_f.START = 1;
    while(M0P_ADC->CR0_f.START !=0);
    r = M0P_ADC->RESULT;
#if (ADC_SCAN_CNT > 0)
    ADC_ALLOW_INT_START_SCAN;        // 允许中断中开启扫描转换
#endif
#else
    // while(mIsBusy);     // 等待扫描转换结束，结束后已经是单次模式
// #if (ADC_IE == 1)   // 如果默认中断是开启的，先要暂时关闭
//     M0P_ADC->CR0_f.IE = 0;
// #endif
    //ADC_DISALLOW_INT_START_SCAN;     // 禁止中断中开启扫描转换
    M0P_ADC->CR0_f.SGLMUX = ch;
    M0P_ADC->SGLSTART = 1;
    while(M0P_ADC->IFR_f.SGLIF==0);
    M0P_ADC->ICR_f.SGLIC = 0;   // 清零转换结束标志位（SGLIF）
    //ADC_DISALLOW_INT_START_SCAN;     // 禁止中断中开启扫描转换
// #if (ADC_IE == 1)   // 如果默认中断是开启的，恢复开启
//     M0P_ADC->CR0_f.IE = 1;
// #endif
    r = M0P_ADC->RESULT;
#endif
    return r;
}

u16 MedianFilter16b9(u16 *dat);
// [同步]获取一次有效的（中值滤波）AD值
// 注意：需要先把对应通道的IO口设置为模拟信号模式
u16 Adc_GetValue(u8 ch)
{
    u8 i;
	u16 ad[9], a;

	for (i = 0; i < 9; i++) //连续采集9次
	{
		ad[i] = Adc_GetOnce(ch);
	}
	a = MedianFilter16b9(ad); //中值滤波
	return a;
}

#if (HC32_MCU_SERIES == HC32F005)
    #define ADCH_VDD_3      9       // 内部VDD/3通道
    #define ADCH_V12        11      // 内部1.2V电压通道
#else
    #define ADCH_VDD_3      27      // 内部VDD/3通道
    #define ADCH_V12        29      // 内部1.2V电压通道
#endif

// 获取VDD电压，单位mV
u16 Adc_GetVDDVoltage()
{
#if (ADC_SREF == 3) // 使用VDD作为参考电压
    u16 v12v = Adc_GetValue(ADCH_V12);
    u16 bv = (u16)((u32)1200*ADC_FULL_VALUE/v12v);
#else
    u16 vddv = Adc_GetValue(ADCH_VDD_3);
    u16 bv = (u32)vddv*(ADC_FULL_VOLTAGE*3)/ADC_FULL_VALUE;
#endif
    return bv;
}



